#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <map>

#include "algorithm/md5.h"
#include "algorithm/aes_impl.h"
#include "algorithm/aes.h"
#include "tool/machine_info.h"
#include "tool/INIParser.h"
//#include "tool/gpu_info.h"

#include <vector>
#include <string>
#include <sstream>


using namespace std;

typedef std::uint64_t hash_t;

constexpr hash_t prime = 0x100000001B3ull;
constexpr hash_t basis = 0xCBF29CE484222325ull;

void process_c(vector<string> &args);

void process_d(vector<string> &args);

void process_e(vector<string> &args);


hash_t hash_(const string str) {
    hash_t ret{basis};

    const char* p_str = str.data();
    while (*p_str) {
        ret ^= *p_str;
        ret *= prime;
        p_str++;
    }

    return ret;
}

constexpr hash_t hash_compile_time(char const *str, hash_t last_value = basis) {
    return *str ? hash_compile_time(str + 1, (*str ^ last_value) * prime) : last_value;
}

void usage() {
    printf("Usage: authentication [options] <inputs> \n\n");
    printf("OPTIONS:\n");
    printf("\t -c <filepath>\n");
    printf("\t -e <src filepath> <encrypted filepath> <key(string len = 32)> <iv(string len = 16)> \n");
    printf("\t -d <src filepath> <decrypted filepath> <key(string len = 32)> <iv(string len = 16)>\n\n");
}


void getOpt(int argc, char *argv[]) {
    if (argc > 1) {
        map<string, vector<string>> map;
        string token;
        for (int i = 1; i < argc; i++) {
            if (strchr(argv[i], '-') != nullptr) {
                token = argv[i];
                vector<string> items;
                map.insert(make_pair(argv[i], items));
            } else {
                auto itr = map.find(token);
                if (map.end() != itr) {
                    itr->second.emplace_back(argv[i]);
                }
            }
        }

        auto itr = map.begin();
        if (itr != map.end()) {
            switch(hash_(itr->first)){
                case hash_compile_time("-c"):
                    process_c(itr->second);
                    break;
                case hash_compile_time("-d"):
                    process_d(itr->second);
                    break;
                case hash_compile_time("-e"):
                    process_e(itr->second);
                    break;
                default:
                    usage();
            }
        }
    }  else {
        usage();
    }
}


void process_c(vector<string>& args) {
    if (args.empty()) {
        cout << "ERROR: illegal parameter: -c {filepath}\n" << endl;
        return;
    }

    FILE *fd = fopen(args[0].c_str(), "wb");
    if (fd == nullptr) {
        cout << "ERROR: can't create file {"<< args[0] <<"}\n" << endl;
        return;
    } else {
        fclose(fd);
    }

    INIParser parser;
    string s;

    if (MachineInfo::getCpuId(s)){
        parser.SetValue("Hardware", "cpu_id", s);
    }

    parser.SetValue("Hardware", "virtual_env",  to_string(MachineInfo::isVirtualMachine()));

    if (MachineInfo::getMacAddress(s)){
        parser.SetValue("Hardware", "mac_address", s);
    }

    if (MachineInfo::getMainboardSerialNumber(s)) {
        parser.SetValue("Hardware", "board_serial", s);
    }

    if (MachineInfo::getDiskSerialNumber(s)) {
        parser.SetValue("Hardware", "disk_serial", s);
    }

//    if (GPUInfo::init()){
//        GPUI_Info info;
//        if (GPUInfo::getInformation(info)) {
//            parser.SetValue("Hardware", "gpu_count", to_string(info.gpu_count));
//            parser.SetValue("Hardware", "gpu_borad", info.board_serial);
//            int index = 0;
//            while(index< info.gpu_list.size()) {
//                parser.SetValue("Gpu", "gpu_name_" + to_string(index), info.gpu_list[index][0]);
//                parser.SetValue("Gpu", "gpu_borad_" + to_string(index), info.gpu_list[index][1]);
//
//                index++;
//            }
//        }
//        GPUInfo::shunDown();
//    } else {
//        std::cout << "ERROR: can't init nvml lib!\n"<< std::endl;
//    }
    parser.WriteINI(args[0]);
}

void process_d(vector<string> &args) {
    if (args.size() < 4) {
        cout << "ERROR: illegal parameter: -e {src filepath} {decrypted filepath} {key(32)} {iv(16)}\n" << endl;
        return;
    }

    if (args[2].length() != 32) {
        cout << "ERROR: illegal key length : key must be 32 length.\n" << endl;
        return;
    }

    if (args[3].length() != 16) {
        cout << "ERROR: illegal iv length : iv must be 16 length.\n" << endl;
        return;
    }

    uint8_t key[32];
    uint8_t iv[16];
    memcpy(key, args[2].data(), 32);
    memcpy(iv, args[3].data(), 16);

    if (AES::cbc256DecryptFile(args[0], args[1], iv, key)) {
        cout << "INFO: process cbc256DecryptFile success!\n" << endl;
    } else {
        cout << "INFO: process cbc256DecryptFile failed!\n" << endl;
    }
}

void process_e(vector<string>& args) {
    if (args.size() < 4) {
        cout << "ERROR: illegal parameter: -e {src filepath} {encrypted filepath} {key(32)} {iv(16)}\n" << endl;
        return;
    }

    if (args[2].length() != 32) {
        cout << "ERROR: illegal key length : key must be 32 length.\n" << endl;
        return;
    }

    if (args[3].length() != 16) {
        cout << "ERROR: illegal iv length : iv must be 16 length.\n" << endl;
        return;
    }

    uint8_t key[32];
    uint8_t iv[16];
    memcpy(key, args[2].data(), 32);
    memcpy(iv, args[3].data(), 16);

    if (AES::cbc256EncryptFile(args[0], args[1], iv, key)) {
        cout << "INFO: process cbc256EncryptFile success!\n" << endl;
    } else {
        cout << "INFO: process cbc256EncryptFile failed!\n" << endl;
    }
}

int main(int argc, char *argv[]) {

     getOpt(argc, argv);

     printf("done!\n");

    return 0;
}